package com.blue.base.oauth.server.config.authorize;

import com.blue.base.oauth.server.config.authorize.code.BataApprovalHandler;
import com.blue.base.oauth.server.config.authorize.code.RedisAuthorizationCodeServices;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;

/**
 * 【步骤一】授权服务适配器：配置授权服务器基础信息
 *
 * @author liulei
 * @version 1.0
 */
@Slf4j
@Configuration
//将session信息托管到redis！ 如果使用@EnableJdbcHttpSession，需要引入包spring-session-jdbc并导入2张表【spring_session、spring_session_attribute】存储集群模式下的http session
//原理：注册一个SessionRepositoryFilter，这个Filter会拦截到所有的请求，并对Session做以下2步操作：1.将token存入到redis,2.将token写入当前浏览器的cookie
//@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600 * 24 * 30, redisNamespace = "cloud:session")session模式
// 默认存一个月，用户登录一次，一个月之内都缓存住用户的登录状态信息
@EnableAuthorizationServer
public class AuthorizationServiceAdapter extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private ObjectProvider<UserDetailsService> userDetailsServiceObjectProvider;
    // -jwt模式
    @Autowired
    private JwtAccessTokenConverter tokenEnhance;
    // -session模式 private final TokenEnhancer tokenEnhance;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private RedisTemplate redisTemplate;
    @Value("${oauth2.type}")
    private String tokenType;

    /**=============================== 必须声明、重写的配置 ================================*/

    /**
     * 【步骤二】客户端凭证信息配置：声明哪个客户端可以来访问授权中心，写死或者通过jdbc来查询获得
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    /**
     * 【步骤三】令牌端点访问配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .tokenServices(tokenServices())
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServicesRedis())
                .userDetailsService(userDetailsServiceObjectProvider.getIfAvailable())
                // 加上下面2个配置，看BetaApprovalHandler的解释
                .userApprovalHandler(userApprovalHandler())
                // session
                //.tokenEnhancer(tokenEnhance)
                // jwt
                .accessTokenConverter(tokenEnhance)
                .approvalStore(approvalStore());
    }

    /**
     * 【步骤四】令牌端点安全策略
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                //oauth/token_key
                .tokenKeyAccess("isAuthenticated()")
                //oauth/check_token
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * =============================== 自定义bean配置================================
     */
    @Bean
    @Primary // 替代默认的DefaultTokenService
    public DefaultTokenServices tokenServices() {
        MyDefaultTokenService tokenService = new MyDefaultTokenService(tokenType, tokenStore, tokenEnhance);
        tokenService.setTokenStore(tokenStore);
        tokenService.setClientDetailsService(jdbcClientDetailsService());
        return tokenService;
    }

    @Bean
    public JdbcClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 配置Redis管理授权码code[临时存储]
     *
     * @return org.springframework.security.oauth2.provider.code.AuthorizationCodeServices
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServicesRedis() {
        return new RedisAuthorizationCodeServices(redisTemplate);
    }

    /**
     * 【步骤五】允许授权码存取code记录存储jdbc
     */
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    @Bean
    public UserApprovalHandler userApprovalHandler() {
        DefaultOAuth2RequestFactory factory = new DefaultOAuth2RequestFactory(jdbcClientDetailsService());
        return new BataApprovalHandler(jdbcClientDetailsService(), approvalStore(), factory);
    }
}
